package com.pig4cloud.pig.common.swagger.plugins;

import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.classmate.TypeResolver;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.pig4cloud.pig.common.swagger.annotation.ApiIgn;
import com.pig4cloud.pig.common.swagger.annotation.ApiInclude;
import io.micrometer.core.instrument.util.StringUtils;
import io.swagger.v3.oas.annotations.media.Schema;
import javassist.*;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.ConstPool;
import javassist.bytecode.annotation.Annotation;
import javassist.bytecode.annotation.StringMemberValue;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import springfox.documentation.builders.RequestParameterBuilder;
import springfox.documentation.builders.SimpleParameterSpecificationBuilder;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.schema.ModelSpecification;
import springfox.documentation.schema.QualifiedModelName;
import springfox.documentation.service.ParameterType;
import springfox.documentation.service.RequestParameter;
import springfox.documentation.service.ResolvedMethodParameter;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.ParameterBuilderPlugin;
import springfox.documentation.spi.service.contexts.ParameterContext;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Collectors;



/**
 * 读取自定义忽略的dto属性并动态生成model
 */
@Slf4j
@Component
@Order
public class SwaggerModelReader extends ParameterBuilderComm implements ParameterBuilderPlugin {
	
    @Autowired
    private TypeResolver typeResolver;
 
    @Override
    public void apply(ParameterContext parameterContext) {
        ResolvedMethodParameter methodParameter = parameterContext.resolvedMethodParameter();
        Optional<ApiIgn> apiIgn = methodParameter.findAnnotation(ApiIgn.class);
        if (apiIgn.isPresent() ) {
			apiIgp(apiIgn.get(),parameterContext);
            return;
        }
        Optional<ApiInclude> apiInclude = methodParameter.findAnnotation(ApiInclude.class);
        if(apiInclude.isPresent()){
			apiInclude(apiInclude.get(),parameterContext);
            return;
		}
    }

    /**
     * 排除字段
     * @param apiIgn
     * @param parameterContext
     */
    private void apiIgp(ApiIgn apiIgn,ParameterContext parameterContext){
			Class<?> originClass = parameterContext.resolvedMethodParameter().getParameterType().getErasedType();
			String name = StrUtil.isEmpty(apiIgn.modelName()) ? originClass.getSimpleName() + "Model" + paramsIndex.incrementAndGet() : apiIgn.modelName();  //model 名称
			Integer annoType = 0;

            try{
                parameterContext
                        .getDocumentationContext()
                        .getAdditionalModels()
                        .add(typeResolver.resolve(createRefModel(apiIgn.value(), name, originClass,annoType, apiIgn.annotation())));  //像documentContext的Models中添加我们新生成的Class
            }catch (Exception e){
                log.error("apiIgp child model:{} err:",originClass.getSimpleName(),e);
                return;
            }
            String namespace = originClass.getPackage().getName();
        //修改model参数的ModelRef为我们动态生成的class(swagger2 3.0源码不支持，需要修改源码 ApiListingScanner，提取扫描之前，需要移动位置)
            parameterContext.requestParameterBuilder()
                .in(ParameterType.BODY)
                .name("params")
                .query(q ->
                        q.allowEmptyValue(false)
                                .model(m->m.referenceModel(rm->rm.key(k->k.qualifiedModelName(new QualifiedModelName(namespace,name)))))
                );
    }

	private void apiInclude(ApiInclude apiInclude,ParameterContext parameterContext){
		Class<?> originClass = parameterContext.resolvedMethodParameter().getParameterType().getErasedType();
        String name = StrUtil.isEmpty(apiInclude.modelName()) ? originClass.getSimpleName() + "Model" + paramsIndex.incrementAndGet() : apiInclude.modelName();  //model 名称
		Integer annoType = 1;
		try {
			parameterContext
                    .getDocumentationContext()
                    .getAdditionalModels()
                    .add(typeResolver.resolve(createRefModel(apiInclude.value(), name, originClass,annoType, apiInclude.annotation())));  //像documentContext的Models中添加我们新生成的Class
		} catch (Exception e) {
            log.error("apiInclude child model:{} err:",originClass.getSimpleName(),e);
            return;
		}

        String namespace = originClass.getPackage().getName();
        //修改model参数的ModelRef为我们动态生成的class(swagger2 3.0源码不支持，需要修改源码 ApiListingScanner，提取扫描之前，需要移动位置)
        parameterContext.requestParameterBuilder()
                .in(ParameterType.BODY)
                .name("params")
                .query(q ->
                        q.allowEmptyValue(false)
                                .model(m->m.referenceModel(rm->rm.key(k->k.qualifiedModelName(new QualifiedModelName(namespace,name)))))
                );
	}
 

    /**
     * 根据propertys中的值动态生成含有Swagger注解的javaBeen
     * @param dealProperties 多个字段，逗号分隔
     * @param name
     * @param origin
     * @param annoType 0=排除，1=限定
     * @param annotation 新的描述，格式：字段名@新的描述
     * @return
     * @throws NotFoundException
     */
    private Class<?> createRefModel(String[] dealProperties, String name, Class<?> origin, Integer annoType, String[] annotation) throws Exception{
    	ClassPool pool = ClassPool.getDefault();
    	CtClass ctClass = null;
        try {
            ctClass = pool.makeClass(origin.getPackage().getName() + "." + name);
            ctClass.stopPruning(true);
        } catch (Exception e) {
        	ctClass = pool.get(origin.getPackage().getName()+"."+name);
        }

        Field[] fields = origin.getDeclaredFields();
                List<Field> fieldAll = Arrays.asList(fields);
                List<String> dealPropertiesList = Splitter.on(",").splitToList(StrUtil.removeAll(Joiner.on(",").join(dealProperties), " "));//去掉空格并用逗号分割
                List<Field> dealFileds = fieldAll.stream().filter(
                        //如果annoType 为空，则
                        f -> annoType == 1 ? dealPropertiesList.contains(f.getName()):(!(dealPropertiesList.contains(f.getName())))
                ).collect(Collectors.toList());
        return createCtFileds(dealFileds, ctClass, annotation);

    }


    @Override
    public boolean supports(DocumentationType delimiter) {
        return true;
    }
 
    public Class<?> createCtFileds(List<Field> keepFileds, CtClass ctClass,String[] annotationNew) throws CannotCompileException, NotFoundException {
    	Map<String, String> annotationNewMap = new HashMap<>();
    	if(ArrayUtil.isNotEmpty(annotationNew)) {
            annotationNewMap = Arrays.asList(annotationNew).stream().map(elem -> elem.split("@")).collect(Collectors.toMap(e -> e[0], e -> e[1]));
    	}
        for (Field field : keepFileds) {
            CtField ctField = new CtField(typeTry(field), field.getName(), ctClass);
            if(ctClass.isFrozen()){
                ctClass.defrost();
            }
            ctField.setModifiers(Modifier.PUBLIC);
            Schema annotation = field.getAnnotation(Schema.class);

            //如果配置了新的描述
            String description = annotationNewMap.containsKey(field.getName())
                    ? annotationNewMap.get(field.getName()) : java.util.Optional.ofNullable(annotation).map(s -> s.description()).orElse("");

            if (StringUtils.isNotEmpty(description)) { //添加model属性说明
                ConstPool constPool = ctClass.getClassFile().getConstPool();

                AnnotationsAttribute attr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
                Annotation ann = new Annotation(Schema.class.getName(), constPool);
                ann.addMemberValue("description", new StringMemberValue(description, constPool));
                attr.addAnnotation(ann);
                ctField.getFieldInfo().addAttribute(attr);
            }
            ctClass.addField(ctField);
        }
        return ctClass.toClass();
    }
}